#pragma once

#include <iostream>
#include <vector>
#include <map>
#include <functional>

#include "json.hpp"

class Platform
{
private:
    std::string PROPERTY_META_FILE_NAME = ".PlatformProject";
    std::string PROPERTY_PROJECT_NAME = "PROJECT_NAME";
    std::string PROPERTY_PROJECT_LOC = "PROJECT_LOCATION";
    std::string PROPERTY_CHIP = "CHIP";
    std::string PROPERTY_PROC = "PROCESSOR";
    std::string PROPERTY_OS = "OS";
    std::string PROPERTY_MSS_FILE_NAME = "MSS_FILE";
    std::string DEFAULT_MSS_FILE_NAME = "system.mss";
    std::string PROPERTY_HPF_FILE_NAME = "HPF_FILE";

    std::string name;
    std::string location;
    std::string chip;
    std::string proc;
    std::string os;
    std::string mss;
    std::string hpf;

    std::map<std::string, std::string> properties;

public:
    std::string getName() { return name; };
    void setName(std::string name) { this->name = name; };
    std::string getLocation() { return location; };
    void setLocation(std::string location) { this->location = location; };
    std::string getChip() { return chip; };
    void setChip(std::string chip) { this->chip = chip; };
    std::string getProc() { return proc; };
    void setProc(std::string proc) { this->proc = proc; };
    std::string getOs() { return os; };
    void setOs(std::string os) { this->os = os; };
    std::string getMss() { return mss; };
    void setMss(std::string mss) { this->mss = mss; };
    std::string getHpf() { return hpf; };
    void setHpf(std::string hpf) { this->hpf = hpf; };
    std::string getFilePath(std::string filename);
    FILE *getFile(std::string filename, const char *mode);
    void create_project(std::string projName, std::string location, std::string chip, std::string proc, std::string os, std::string hpf);
    void saveMetaData();
    void restoreMetaData();
    std::string getProperty(std::string key);
    void setProperty(std::string key, std::string value);
    void loadProperties();
    void saveProperties();
};

class Application
{
private:
    std::string PROPERTY_META_FILE_NAME = ".ApplicationProject";
    std::string PROPERTY_PROJECT_NAME = "PROJECT_NAME";
    std::string PROPERTY_PROJECT_LOC = "PROJECT_LOCATION";
    std::string PROPERTY_LANGUAGE_TYPE = "LANGUAGE_TYPE";
    std::string PROPERTY_BSP_NAME = "BSP_NAME";
    std::string PROPERTY_BSP_LOC = "BSP_LOCATION";

    std::string name;
    std::string location;
    std::string language;
    std::string bsp_name;
    std::string bsp_loc;

    Platform platform;

    std::map<std::string, std::string> properties;

public:
    std::string getName() { return name; };
    void setName(std::string name) { this->name = name; };
    std::string getLocation() { return location; };
    void setLocation(std::string location) { this->location = location; };
    std::string getLanguage() { return language; };
    void setLanguage(std::string language) { this->language = language; };
    std::string getBspName() { return bsp_name; };
    void setBspName(std::string bsp_name) { this->bsp_name = bsp_name; };
    std::string getBspLoc() { return bsp_loc; };
    void setBspLoc(std::string bsp_loc) { this->bsp_loc = bsp_loc; };
    std::string getFilePath(std::string filename);
    FILE *getFile(std::string filename, const char *mode);
    void create_project(std::string projName, std::string location, std::string language, std::string bsp_loc);
    void saveMetaData();
    void restoreMetaData();
    std::string getProperty(std::string key);
    void setProperty(std::string key, std::string value);
    void loadProperties();
    void saveProperties();
};

class bsp_tool
{
public:
    // Function Tools
    nlohmann::json get_cfg_file(std::string file_path);
    nlohmann::json get_newest(nlohmann::json doc);

    std::string get_basename(std::string name, std::string trim_prefix = "");
    std::string get_version(std::string name);
    std::string concat_name_version(std::string name, std::string version);
    std::string get_regex_version(std::string version);
    bool is_library(std::string name);

    std::string get_os_name(std::string os);
    std::string get_chip_name(std::string chip);

    void append_lib_depends(std::vector<std::string>& depends, nlohmann::json doc, std::string depend);
    void append_src_depends(std::vector<std::string> &depends, nlohmann::json doc, std::string depend);
    std::vector<std::string> get_src_depends(nlohmann::json doc, nlohmann::json target);
    std::vector<std::string> get_src_depends_without_libs(nlohmann::json doc, nlohmann::json target);
    std::vector<std::string> get_bsp_src_depends(nlohmann::json doc);
    std::vector<std::string> get_lib_depends(nlohmann::json doc, nlohmann::json target);
    void set_os_from_config(nlohmann::json &doc, nlohmann::json cfg);
    void set_driver_from_config(nlohmann::json &doc, nlohmann::json cfg);
    void set_library_from_config(nlohmann::json &doc, nlohmann::json cfg);

    // Config File Parser
    std::vector<std::string> get_os_names();
    std::vector<std::string> get_os_versions(std::string name);
    std::vector<std::string> get_driver_names();
    std::vector<std::string> get_driver_versions(std::string name);
    std::vector<std::string> get_library_names();
    std::vector<std::string> get_library_versions(std::string name);
    nlohmann::json get_os_from_cfgfile(std::string name, std::string version);
    nlohmann::json get_driver_from_cfgfile(std::string name, std::string version);
    nlohmann::json get_library_from_cfgfile(std::string name, std::string version);

    // HPF Parser
    std::string get_chip_from_hpf(std::string hpf);

    // BSP Tools
    Platform load_platform_project(std::string location);
    void create_platform_project(std::string projName, std::string location, std::string chip, std::string proc, std::string os, std::string hpf);
    void create_default_mss(std::string mssfile, std::string chip, std::string proc, std::string os);
    std::string get_processor(std::string mssfile);
    void set_processor(std::string mssfile, std::string name);
    nlohmann::json get_os(std::string mssfile);
    nlohmann::json get_doc(std::string mssfile);
    void set_os_version(std::string mssfile, std::string name, std::string version);
    nlohmann::json get_drivers(std::string mssfile);
    nlohmann::json get_driver(std::string mssfile, std::string name);
    void set_driver_version(std::string mssfile, std::string name, std::string version);
    void add_driver(std::string mssfile, std::string name, std::string version);
    void delete_driver(std::string mssfile, std::string name);
    nlohmann::json get_libraries(std::string mssfile);
    nlohmann::json get_library(std::string mssfile, std::string name);
    void set_library_version(std::string mssfile, std::string name, std::string version);
    void add_library(std::string mssfile, std::string name, std::string version);
    void delete_library(std::string mssfile, std::string name);
    
    // App Tools
    void create_application_project(std::string projName, std::string location, std::string language, std::string bsp_loc);
    Application load_application_project(std::string location);
    std::vector<std::string> get_app_names();
    std::vector<std::string> get_standalone_app_names();
    std::vector<std::string> get_freertos_app_names();
    std::vector<std::string> get_rtthread_app_names();
    std::vector<std::string> get_linux_app_names();
    std::vector<std::string> get_driver_app_names(std::string driver_name);
    nlohmann::json get_app(std::string name);
    std::string get_app_desc(std::string name);
    std::vector<std::string> get_app_libs(std::string name);
    std::vector<std::string> get_app_extra_libs(std::string mssfile, std::string name);
    std::map<std::string, std::string> get_app_build_properties(std::string app_name);

    static int validate(std::map<std::string, std::string> args, std::map<std::string, std::string> needed)
    {
        std::string error_msg = "Wrong arguments! needed: " + args_to_str(needed);

        if (args.size() != needed.size())
        {
            std::cout << error_msg << std::endl;
        }

        for (auto item : args)
        {
            if (needed.find(item.first) == needed.end())
            {
                std::cout << error_msg << std::endl;
                return -1;
            }
            else if (item.second.empty())
            {
                std::cout << error_msg << std::endl;
                return -1;
            }
        }

        return 0;
    }

    static std::string args_to_str(std::map<std::string, std::string> args)
    {
        std::string str = "";
        if (!args.empty())
        {
            for (auto entry : args)
            {
                str = str + entry.first + " " + entry.second + " ";
            }
        }
        return str;
    }

    static std::string vec_to_str(std::vector<std::string> args)
    {
        std::string str = "";
        if (!args.empty())
        {
            for (std::string entry : args)
            {
                str += entry;
                if (entry != args[args.size() - 1])
                    str += ", ";
            }
        }
        return str;
    }

    static std::string map_to_str(std::map<std::string, std::string> args)
    {
        std::string str = "";
        if (!args.empty())
        {
            for (auto entry : args)
            {
                if (entry.second.empty()) {
                    str = str + entry.first;
                } else {
                    str = str + entry.first + "=" + entry.second;
                }
                if (entry.first != args.rbegin()->first)
                    str = str + ", ";
            }
        }
        return str;
    }

    static std::string json_to_str(nlohmann::json doc)
    {
        return doc.dump();
    }

    static void print_json(nlohmann::json doc)
    {
        if (doc != NULL && !doc.empty())
        {
            std::cout << doc.dump() << std::endl;
        }
    }

    static void print_map(std::map<std::string, std::string> args)
    {
        if (!args.empty())
        {
            for (auto entry : args)
            {
                if (entry.second.empty()) {
                    std::cout << entry.first;
                } else {
                    std::cout << entry.first + "=" + entry.second;
                }
                if (entry.first != args.rbegin()->first)
                    std::cout << ", ";
            }
            std::cout << std::endl;
        }
    }

    static void print_vector(std::vector<std::string> vec)
    {
        if (!vec.empty())
        {
            for (std::string item : vec)
            {
                std::cout << item;
                if (item != vec[vec.size() - 1])
                    std::cout << ", ";
            }
            std::cout << std::endl;
        }
    }

    static void print_string(std::string str)
    {
        if (!str.empty() && str.length() > 0)
        {
            std::cout << str << std::endl;
        }
    }

public:
    // Function Tools
    virtual std::string get_cpu_name(std::string proc) = 0;

    virtual std::map<std::string, std::string> new_makefile_replaced() = 0;
    virtual void process_platform_file(std::string platform_dir, std::string hpf) = 0;
    virtual void process_bsp_makefile(std::string bsp_loc) = 0;
    virtual void process_app_makefile(std::string bsp_loc, std::string app_loc, std::string app_name) = 0;

    virtual std::vector<std::string> get_include_paths(std::string bsp_loc) = 0;

    virtual std::vector<std::string> get_link_scripts(std::string app_loc, std::string chip, std::string download) = 0;

    // HPF Parser
    virtual std::map<std::string, std::string> get_drivers_from_hpf(std::string hpf, int type) = 0;

    // BSP Tools
    virtual void create_mss_from_hpf(std::string hpf, std::string mssfile, std::string chip, std::string proc, std::string os) = 0;
    virtual void generate_bsp_sources(std::string hpf, std::string mssfile, std::string bsp_loc) = 0;
    virtual void regenerate_bsp_sources(std::string mssfile, std::string bsp_loc) = 0;
    virtual void update_bsp_hpf(std::string bsp_loc, std::string hpf) = 0;

    // App Tools
    virtual void generate_app_sources(std::string bsp_loc, std::string name, std::string app_loc) = 0;
    
public:
    nlohmann::json sdk_cfg;
    std::string sdk_root_path;

    bsp_tool();
    virtual ~bsp_tool();

    using func_ptr = std::function<void(std::map<std::string, std::string>)>;

    std::map<std::string, func_ptr> func_map;

    void execute(std::string func_name, std::map<std::string, std::string> args);
};
